import os
import sys

from PIL import Image
import numpy as np
from PyQt5.QtCore import Qt, QThread, QSettings, pyqtSignal
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import QApplication, QDialog, QFileDialog, QMessageBox, QPushButton, QSlider, QTextEdit, QWidget, QMainWindow, QVBoxLayout, QHBoxLayout, QLabel, QProgressBar, QSplitter, QFrame, QGroupBox, QGridLayout, QDoubleSpinBox, QCheckBox, QLineEdit

import qdarkstyle

# 注释掉缺失的模块导入
# from image_review_dialog import ImageReviewDialog
# from threshold_settings_dialog import ThresholdSettingsDialog

# 添加占位类以避免运行时错误
class ImageReviewDialog(QDialog):
    def __init__(self, potential_empty_images, parent=None):
        super().__init__(parent)
        self.potential_empty_images = potential_empty_images
        self.rejected_images = []  # 存储用户确认要拒绝的图片
        self.setWindowTitle("空白图片确认")
        self.setGeometry(100, 100, 900, 700)
        
        # 添加主布局
        main_layout = QVBoxLayout()
        
        # 添加标题标签
        title_label = QLabel(f"检测到 {len(potential_empty_images)} 张可能的空白图片。请检查并确认。双击缩略图可打开原图。")
        main_layout.addWidget(title_label)
        
        # 导入需要的组件
        from PyQt5.QtWidgets import QScrollArea, QGridLayout, QCheckBox
        from PyQt5.QtGui import QPixmap
        
        # 创建滚动区域
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        
        # 创建缩略图网格布局容器
        thumbnail_widget = QWidget()
        grid_layout = QGridLayout(thumbnail_widget)
        
        # 显示缩略图
        self.thumbnail_labels = []
        for i, img_path in enumerate(potential_empty_images):
            try:
                # 创建缩略图
                pixmap = QPixmap(img_path)
                if not pixmap.isNull():
                    # 缩放为150x150的缩略图
                    scaled_pixmap = pixmap.scaled(150, 150, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                    
                    # 创建标签显示缩略图
                    thumbnail_label = QLabel()
                    thumbnail_label.setPixmap(scaled_pixmap)
                    thumbnail_label.setAlignment(Qt.AlignCenter)
                    thumbnail_label.setMinimumSize(160, 160)
                    thumbnail_label.setObjectName(f"thumbnail_{i}")
                    thumbnail_label.setToolTip(img_path)
                    
                    # 启用双击事件
                    thumbnail_label.mouseDoubleClickEvent = lambda event, path=img_path: self.open_image(path)
                    
                    # 创建复选框（默认为选中，即标记为空白图片）
                    checkbox = QCheckBox("标记为空白")
                    checkbox.setChecked(True)
                    checkbox.stateChanged.connect(lambda state, path=img_path: self.update_rejected_list(state, path))
                    
                    # 创建图片信息标签
                    file_name = os.path.basename(img_path)
                    info_label = QLabel(file_name)
                    info_label.setWordWrap(True)
                    info_label.setMaximumWidth(150)
                    
                    # 添加到网格
                    row = i // 4  # 每行4张图片
                    col = i % 4
                    grid_layout.addWidget(thumbnail_label, row*3, col)
                    grid_layout.addWidget(info_label, row*3+1, col)
                    grid_layout.addWidget(checkbox, row*3+2, col)
                    
                    self.thumbnail_labels.append(thumbnail_label)
                    
                    # 默认添加到拒绝列表
                    self.rejected_images.append(img_path)
                else:
                    # 如果无法加载图片，添加文本标签
                    text_label = QLabel(f"无法加载图片\n{os.path.basename(img_path)}")
                    text_label.setAlignment(Qt.AlignCenter)
                    text_label.setMinimumSize(160, 160)
                    grid_layout.addWidget(text_label, i//4*3, i%4)
                    checkbox = QCheckBox("标记为空白")
                    checkbox.setChecked(True)
                    checkbox.stateChanged.connect(lambda state, path=img_path: self.update_rejected_list(state, path))
                    grid_layout.addWidget(checkbox, i//4*3+2, i%4)
                    self.rejected_images.append(img_path)
            except Exception as e:
                error_label = QLabel(f"加载错误\n{os.path.basename(img_path)}")
                error_label.setAlignment(Qt.AlignCenter)
                error_label.setMinimumSize(160, 160)
                grid_layout.addWidget(error_label, i//4*3, i%4)
        
        # 设置滚动区域的部件
        scroll_area.setWidget(thumbnail_widget)
        main_layout.addWidget(scroll_area)
        
        # 添加按钮布局
        button_layout = QHBoxLayout()
        button_layout.addStretch()
        
        # 添加全选按钮
        select_all_btn = QPushButton("全选")
        select_all_btn.clicked.connect(self.select_all)
        button_layout.addWidget(select_all_btn)
        
        # 添加全不选按钮
        deselect_all_btn = QPushButton("全不选")
        deselect_all_btn.clicked.connect(self.deselect_all)
        button_layout.addWidget(deselect_all_btn)
        
        # 添加确认按钮
        ok_button = QPushButton("确认")
        ok_button.clicked.connect(self.accept)
        ok_button.setDefault(True)
        button_layout.addWidget(ok_button)
        
        # 添加取消按钮
        cancel_button = QPushButton("取消")
        cancel_button.clicked.connect(self.reject)
        button_layout.addWidget(cancel_button)
        
        main_layout.addLayout(button_layout)
        self.setLayout(main_layout)
    
    def update_rejected_list(self, state, img_path):
        """更新拒绝列表"""
        if state == Qt.Checked and img_path not in self.rejected_images:
            self.rejected_images.append(img_path)
        elif state == Qt.Unchecked and img_path in self.rejected_images:
            self.rejected_images.remove(img_path)
    
    def select_all(self):
        """全选所有复选框"""
        for checkbox in self.findChildren(QCheckBox):
            checkbox.setChecked(True)
    
    def deselect_all(self):
        """取消全选所有复选框"""
        for checkbox in self.findChildren(QCheckBox):
            checkbox.setChecked(False)
    
    def open_image(self, img_path):
        """双击打开原始图片"""
        try:
            import subprocess
            if sys.platform == 'win32':
                os.startfile(img_path)  # Windows系统使用默认程序打开
            elif sys.platform == 'darwin':  # macOS
                subprocess.run(['open', img_path])
            else:  # Linux等其他系统
                subprocess.run(['xdg-open', img_path])
        except Exception as e:
            QMessageBox.warning(self, "打开失败", f"无法打开图片: {str(e)}")
    
    def get_result(self):
        """获取用户选择的结果"""
        # 返回接受和拒绝的图片列表
        accepted = [img for img in self.potential_empty_images if img not in self.rejected_images]
        return {
            'accepted': accepted,
            'rejected': self.rejected_images
        }

class ThresholdSettingsDialog(QDialog):
    """阈值设置对话框"""
    def __init__(self, default_non_white_threshold=0.01, parent=None):
        super().__init__(parent)
        self.setWindowTitle("阈值设置")
        self.resize(400, 200)
        
        # 保存阈值设置
        self.non_white_threshold = default_non_white_threshold
        
        # 创建布局
        self._init_ui()
    
    def _init_ui(self):
        """初始化界面"""
        # 创建主布局
        layout = QVBoxLayout(self)
        
        # 创建阈值设置组
        threshold_group = QGroupBox("阈值设置")
        threshold_layout = QVBoxLayout()
        
        # 暗色像素比例阈值
        threshold_label = QLabel("暗色像素比例阈值 (%)")
        self.threshold_spinbox = QDoubleSpinBox()
        self.threshold_spinbox.setRange(0.01, 100.0)
        self.threshold_spinbox.setDecimals(2)
        self.threshold_spinbox.setSingleStep(0.01)
        self.threshold_spinbox.setValue(self.non_white_threshold * 100)  # 转换为百分比显示
        
        # 添加到布局
        threshold_layout.addWidget(threshold_label)
        threshold_layout.addWidget(self.threshold_spinbox)
        threshold_group.setLayout(threshold_layout)
        
        # 创建按钮布局
        button_layout = QHBoxLayout()
        self.ok_button = QPushButton("确定")
        self.cancel_button = QPushButton("取消")
        
        button_layout.addWidget(self.ok_button)
        button_layout.addWidget(self.cancel_button)
        
        # 连接信号槽
        self.ok_button.clicked.connect(self.accept)
        self.cancel_button.clicked.connect(self.reject)
        
        # 添加到主布局
        layout.addWidget(threshold_group)
        layout.addLayout(button_layout)
    
    def get_thresholds(self):
        """获取设置的阈值"""
        # 将百分比转换回小数
        non_white_threshold = self.threshold_spinbox.value() / 100
        return {"non_white_threshold": non_white_threshold}

class ConversionThread(QThread):
    """转换线程"""
    # 添加所需的信号
    progress_updated = pyqtSignal(int)
    log_updated = pyqtSignal(str)
    conversion_complete = pyqtSignal(bool, str)
    screening_complete = pyqtSignal(dict)
    review_images_required = pyqtSignal(list)
    
    def __init__(self, source_dir, target_dir, enable_screening=True, screening_only=False,
                 non_white_threshold=0.01, compress_quality=85, max_pdf_size_mb=2.0,
                 rejected_images=None, debug_mode=False):
        super().__init__()
        self.source_dir = source_dir
        self.target_dir = target_dir
        self.enable_screening = enable_screening
        self.screening_only = screening_only
        self.non_white_threshold = non_white_threshold
        self.compress_quality = compress_quality
        self.max_pdf_size_mb = max_pdf_size_mb
        # 规范化全局拒绝列表中的路径，确保比较一致性
        self.rejected_images = [os.path.normpath(p) for p in (rejected_images or [])]
        self.debug_mode = debug_mode
        self.running = True
        self.all_potential_empty_images = []  # 收集所有潜在的空白图片
    
    def stop(self):
        """停止线程"""
        self.running = False
    
    def run(self):
        """线程执行的主方法"""
        try:
            if self.screening_only:
                # 仅执行筛查操作
                self._screen_all_folders()
            else:
                # 执行转换操作
                self._convert_all_folders()
        except Exception as e:
            self.log_updated.emit(f"线程执行出错: {str(e)}")
            if self.screening_only:
                self.screening_complete.emit({"total_folders": 0, "success_count": 0, "fail_count": 0, "total_empty_images": 0})
            else:
                self.conversion_complete.emit(False, f"线程执行出错: {str(e)}")
    
    def _screen_all_folders(self):
        """筛查所有文件夹中的空白图片（并行处理优化）"""
        import concurrent.futures
        from concurrent.futures import ThreadPoolExecutor
        
        total_folders = 0
        processed_folders = 0
        success_count = 0
        fail_count = 0
        total_empty_images = 0
        
        try:
            # 第一步：预先计算总叶文件夹数
            leaf_folders = []
            for root, dirs, files in os.walk(self.source_dir):
                if not self.running:
                    break
                if self.is_leaf_folder(root):
                    leaf_folders.append(root)
            
            total_folders = len(leaf_folders)
            
            # 记录总文件夹数
            self.log_updated.emit(f"预扫描完成，总计 {total_folders} 个叶文件夹需要筛查")
            
            # 第二步：并行执行筛查，使用更多线程以提高CPU利用率
            # 根据CPU核心数设置线程池大小，使用2-3倍CPU核心数
            max_workers = min(os.cpu_count() * 3, 32)  # 上限32个线程避免资源耗尽
            self.log_updated.emit(f"启动并行筛查，使用 {max_workers} 个线程")
            
            with ThreadPoolExecutor(max_workers=max_workers) as executor:
                future_to_folder = {}
                
                # 提交所有筛查任务
                for folder_path in leaf_folders:
                    if not self.running:
                        break
                    future = executor.submit(self.screen_folder_images, folder_path)
                    future_to_folder[future] = folder_path
                
                # 收集结果并更新进度
                for future in concurrent.futures.as_completed(future_to_folder):
                    if not self.running:
                        break
                    
                    folder_path = future_to_folder[future]
                    try:
                        processed_folders += 1
                        folder_empty_images = future.result()
                        
                        if folder_empty_images:
                            success_count += 1
                            total_empty_images += len(folder_empty_images)
                            # 将当前文件夹的潜在空白图片添加到全局列表
                            self.all_potential_empty_images.extend(folder_empty_images)
                        else:
                            fail_count += 1
                        
                        # 更新进度
                        if total_folders > 0:
                            progress = int((processed_folders / total_folders) * 99)  # 最高到99%
                        else:
                            progress = 0
                        self.progress_updated.emit(progress)
                        
                    except Exception as e:
                        fail_count += 1
                        self.log_updated.emit(f"处理文件夹 {folder_path} 时出错: {str(e)}")
            
            # 所有文件夹筛查完成后，如果有潜在空白图片，统一发送确认请求
            if self.all_potential_empty_images and self.running:
                self.log_updated.emit(f"\n总共发现 {len(self.all_potential_empty_images)} 张潜在的空白图片")
                self.log_updated.emit("等待用户确认...")
                
                # 发送信号给主线程进行用户确认
                self.review_images_required.emit(self.all_potential_empty_images)
                
                # 等待主线程处理用户确认结果
                while self.running and not hasattr(self, 'review_result'):
                    self.msleep(100)
                
                # 记录结果
                if self.running and hasattr(self, 'review_result'):
                    rejected_count = len(self.review_result.get('rejected', []))
                    self.log_updated.emit(f"用户确认排除 {rejected_count} 张图片")
                    # 更新拒绝的图片列表
                    self.rejected_images = self.review_result.get('rejected', [])
                    total_empty_images = rejected_count
        finally:
            # 发送筛查完成信号
            self.screening_complete.emit({
                "total_folders": total_folders,
                "success_count": success_count,
                "fail_count": fail_count,
                "total_empty_images": total_empty_images
            })
    
    def _convert_all_folders(self):
        """高性能转换所有文件夹中的图片为PDF（多核优化版）"""
        import concurrent.futures
        from concurrent.futures import ThreadPoolExecutor
        import time
        import shutil
        from itertools import repeat
        
        start_time = time.time()
        total_folders = 0
        success_count = 0
        fail_count = 0
        futures = []
        
        # 首先检查是否已经被取消
        if not self.running:
            if hasattr(self, 'debug_mode') and self.debug_mode:
                self.log_updated.emit("❌ 任务已被取消，不执行转换")
            self.conversion_complete.emit(False, "任务已被取消")
            return
            
        try:
            # 获取CPU核心数，用于动态确定线程数
            cpu_count = os.cpu_count() or 4
            max_workers_high = min(cpu_count * 3, 32)  # 更高的线程池上限
            
            # 预创建目标目录
            if self.target_dir and not os.path.exists(self.target_dir):
                os.makedirs(self.target_dir, exist_ok=True)
            
            # 优化的文件夹扫描 - 使用生成器表达式提高内存效率
            leaf_folders = []
            for root, dirs, files in os.walk(self.source_dir):
                if not self.running:
                    break
                # 使用更高效的图片检测方法
                has_images = False
                for f in files:
                    if self.is_image_file(f):
                        has_images = True
                        break
                
                if self.is_leaf_folder(root) and has_images:
                    # 只存储必要信息，减少内存占用
                    leaf_folders.append((root, files))
            
            total_folders = len(leaf_folders)
            
            # 并行处理文件夹转换 - 使用更大的线程池
            with ThreadPoolExecutor(max_workers=max_workers_high) as executor:
                # 提交所有转换任务
                futures = []
                # 提交任务时就开始显示一些初始进度
                for idx, (root, files) in enumerate(leaf_folders):
                    if not self.running:
                        break
                    
                    # 生成输出路径 - 修改为取消最后一层文件夹，将PDF放在上一级目录
                    folder_name = os.path.basename(root)
                    
                    if self.target_dir:
                        # 获取相对于源目录的路径
                        relative_path = os.path.relpath(root, self.source_dir)
                        # 获取上一级目录路径
                        parent_relative_path = os.path.dirname(relative_path)
                        # 创建输出父目录（上一级）
                        output_parent_dir = os.path.join(self.target_dir, parent_relative_path)
                        os.makedirs(output_parent_dir, exist_ok=True)
                        # 生成PDF文件路径 - 直接放在上一级目录
                        output_pdf_path = os.path.join(output_parent_dir, f"{folder_name}.pdf")
                    else:
                        # 在源目录情况下，也放在上一级目录
                        parent_dir = os.path.dirname(root)
                        output_pdf_path = os.path.join(parent_dir, f"{folder_name}.pdf")
                    
                    # 提交任务并记录元数据
                    future = executor.submit(self._process_single_folder, root, files, output_pdf_path)
                    futures.append((future, root, output_pdf_path, idx))
                    
                    # 任务提交时就更新一点进度，让用户看到转换已经开始
                    if total_folders > 0 and idx % max(1, total_folders // 10) == 0:
                        initial_progress = min(25, int((idx + 1) / total_folders * 25))  # 初始进度限制在0-25%之间
                        self.progress_updated.emit(initial_progress)
                
                # 收集结果 - 优化的并行结果处理
                processed_count = 0
                for future, root, output_pdf_path, idx in futures:
                    if not self.running:
                        # 尝试取消未完成的任务
                        future.cancel()
                        continue
                    
                    try:
                        # 设置超时，避免单个慢任务阻塞整体进度
                        success = future.result(timeout=600)  # 10分钟超时
                        if success:
                            success_count += 1
                        else:
                            fail_count += 1
                        
                        # 更新进度 - 确保从初始进度平滑过渡到实际完成进度
                        processed_count += 1
                        if total_folders > 0:
                            # 从25%开始，到99%结束，确保进度条持续移动
                            base_progress = 25  # 基于初始进度的基准值
                            remaining_progress = 99 - base_progress
                            progress = base_progress + int((processed_count / total_folders) * remaining_progress)
                            progress = min(progress, 99)  # 最高到99%
                        else:
                            progress = 0
                        self.progress_updated.emit(progress)
                    except concurrent.futures.CancelledError:
                        fail_count += 1
                    except concurrent.futures.TimeoutError:
                        fail_count += 1
                        self.log_updated.emit(f"文件夹处理超时: {os.path.basename(root)}")
                    except Exception as e:
                        fail_count += 1
                        if hasattr(self, 'debug_mode') and self.debug_mode:
                            self.log_updated.emit(f"处理文件夹时出错: {str(e)[:50]}...")
        finally:
            # 确保所有任务都被取消
            for future, _, _, _ in futures:
                if not future.done():
                    future.cancel()
            
            # 计算总耗时
            elapsed_time = time.time() - start_time
            
            # 设置进度条为100%，表示任务完成
            self.progress_updated.emit(100)
            
            # 发送转换完成信号
            success = success_count > 0
            message = f"转换完成: 总计 {total_folders} 个文件夹, 成功 {success_count} 个, 失败 {fail_count} 个, 耗时 {elapsed_time:.2f}秒"
            self.conversion_complete.emit(success, message)
    
    def _process_single_folder(self, folder_path, files, output_pdf_path):
        """处理单个文件夹的转换（用于并行执行）"""
        try:
            # 快速收集图片文件
            image_files = []
            for f in files[:300]:  # 限制处理数量
                if self.is_image_file(f):
                    image_files.append(os.path.join(folder_path, f))
            
            if not image_files:
                return False
            

            
            # 使用自然数字排序（让10.png排在9.png后面）
            import re
            def natural_sort_key(s):
                return [int(text) if text.isdigit() else text.lower() for text in re.split('([0-9]+)', os.path.basename(s))]
            
            image_files.sort(key=natural_sort_key)
            
            # 排序完成，继续处理
            
            # 应用全局排除列表（如果启用筛选）
            if self.enable_screening:
                # 记录全局排除列表信息
                if self.debug_mode:
                    self.log_updated.emit(f"📋 全局排除列表大小: {len(self.rejected_images)}")
                
                # 转换为集合以提高查找效率
                rejected_images_set = set(self.rejected_images)
                
                # 过滤排除的图片
                filtered_images = []
                excluded_count = 0
                
                for img in image_files:
                    # 规范化当前图片路径以确保比较一致性
                    normalized_img_path = os.path.normpath(img)
                    
                    # 使用完整路径匹配全局排除列表
                    if normalized_img_path in rejected_images_set:
                        excluded_count += 1
                        if self.debug_mode:
                            self.log_updated.emit(f"🎯 全局排除匹配: {img} (路径精确匹配)")
                        # 只记录前5个被排除的文件路径作为示例
                        if excluded_count <= 5:
                            self.log_updated.emit(f"📄 排除空白图片: {img} (全局排除)")
                    else:
                        filtered_images.append(img)
                
                # 记录详细的排除统计信息
                if excluded_count > 0:
                    # 获取当前文件夹名称
                    folder_name = os.path.basename(folder_path)
                    self.log_updated.emit(f"📄 总计排除 {excluded_count} 个图片 (全局排除)")
                    self.log_updated.emit(f"📄 文件夹 '{folder_name}' 中将排除 {excluded_count} 张空白图片后进行转换")
                    if excluded_count > 5:
                        self.log_updated.emit(f"📄 还有 {excluded_count - 5} 个图片被排除...")
                
                # 更新过滤后的图片列表
                image_files = filtered_images
            
            if not image_files:
                return False
            
            # 直接调用PIL转换
            return self.convert_with_pil(image_files, output_pdf_path)
        except Exception:
            return False
        
    
    def is_leaf_folder(self, folder_path):
        """检查文件夹是否为叶文件夹（不包含子文件夹）"""
        try:
            items = os.listdir(folder_path)
            for item in items:
                item_path = os.path.join(folder_path, item)
                if os.path.isdir(item_path):
                    return False
            return True
        except Exception as e:
            self.log_updated.emit(f"检查文件夹 {folder_path} 时出错: {e}")
            return False
    
    def is_image_file(self, file_path):
        """检查文件是否为图片"""
        extension = os.path.splitext(file_path)[1].lower()
        return extension in {'.jpg', '.jpeg', '.png', '.bmp', '.gif'}
    
    def is_empty_page(self, image_path):
        """空白页面检测 - 使用暗色像素比例作为筛查标准
        
        Args:
            image_path: 图片路径
            
        Returns:
            bool: 如果暗色像素比例低于设定阈值，认为是空白图片，返回True
        """
        import os
        import numpy as np
        
        # 只在debug模式下导入logging
        debug_mode = getattr(self, 'debug_mode', False)
        
        try:
            # 直接使用完整路径
            with Image.open(image_path) as img:
                # 1. 适当缩小图片尺寸以提高处理速度，同时保持足够细节
                max_size = 300
                if img.width > max_size or img.height > max_size:
                    scale_factor = max_size / max(img.width, img.height)
                    new_width = int(img.width * scale_factor)
                    new_height = int(img.height * scale_factor)
                    # 使用快速缩放算法提高性能
                    img = img.resize((new_width, new_height), Image.BILINEAR)
                
                # 2. 转换为灰度图
                if img.mode != 'L':
                    img = img.convert('L')
                
                # 3. 使用NumPy加速像素处理
                # 将PIL图像转换为NumPy数组
                img_array = np.array(img)
                total_pixels = img_array.size
                
                # 4. 快速计算暗色像素数量（使用NumPy向量化操作）
                # 像素值小于220的视为暗色像素（表示文字或内容）
                dark_pixel_count = np.sum(img_array < 220)
                
                # 5. 计算暗色像素比例
                dark_pixel_ratio = dark_pixel_count / total_pixels if total_pixels > 0 else 0
                
                # 6. 获取用户设置的阈值（现在作为暗色像素比例阈值）
                try:
                    # 尝试获取用户设置的阈值（小数形式，如0.01表示1%）
                    dark_threshold = getattr(self, 'non_white_threshold', 0.01)
                    
                    # 阈值合理性检查：确保阈值在合理范围内（0.001%到5%之间）
                    if dark_threshold < 0.00001 or dark_threshold > 0.05:
                        dark_threshold = 0.01  # 如果超出范围，使用默认值1%
                except Exception:
                    # 如果获取或处理阈值时出错，使用默认值
                    dark_threshold = 0.01  # 1%
                
                # 7. 单一条件判断：只使用暗色像素比例与用户设置的阈值比较
                is_empty = dark_pixel_ratio < dark_threshold
                
                # 只在debug模式下输出详细日志
                if debug_mode:
                    debug_msg = f"空白检测 - 图片: {os.path.basename(image_path)}, " \
                               f"暗色像素比例: {dark_pixel_ratio*100:.3f}%, " \
                               f"阈值: {dark_threshold*100:.3f}%, " \
                               f"结果: {'空白' if is_empty else '非空白'}"
                    
                    # 尝试输出到应用日志
                    try:
                        if hasattr(self, 'log_updated'):
                            self.log_updated.emit(debug_msg)
                    except Exception:
                        pass
                
                return is_empty
        except Exception as e:
            error_msg = f"处理图片 {image_path} 时发生异常: {str(e)}"
            logging.debug(error_msg)
            try:
                if hasattr(self, 'log_updated'):
                    self.log_updated.emit(error_msg)
            except Exception:
                pass
            # 出错时默认不视为空白
            return False
    
    def convert_images_to_pdf(self, folder_path, output_pdf_path, rejected_images=None):
        """将文件夹中的图片转换为PDF文件"""
        # 使用传入的rejected_images或使用默认值
        if rejected_images is None:
            rejected_images = self.rejected_images or set()
        
        # 记录函数开始（减少日志输出）
        if hasattr(self, 'debug_mode') and self.debug_mode:
            self.log_updated.emit(f"开始转换文件夹: {folder_path}")
        
        try:
            # 获取所有图片文件
            image_files = []
            items = os.listdir(folder_path)
            
            for item in items:
                # 检查是否已取消
                if not self.running:
                    self.log_updated.emit("❌ 用户取消，中断图片收集")
                    return False
                    
                item_path = os.path.join(folder_path, item)
                if os.path.isfile(item_path) and self.is_image_file(item_path):
                    # 检查是否在排除列表中（使用精确路径比较）
                    if rejected_images and os.path.normpath(item_path) in rejected_images:
                        continue
                    
                    image_files.append(item_path)
            
            # 直接添加日志：排序前的图片列表，不依赖debug_mode检查
            self.log_updated.emit(f"📂 排序前图片列表 (文件夹: {os.path.basename(folder_path)}):")
            for i, img_path in enumerate(image_files):
                self.log_updated.emit(f"  排序前[{i}]: {os.path.basename(img_path)}")
            
            # 使用自然数字排序（让10.png排在9.png后面）
            import re
            def natural_sort_key(s):
                return [int(text) if text.isdigit() else text.lower() for text in re.split('([0-9]+)', os.path.basename(s))]
            
            image_files.sort(key=natural_sort_key)
            
            # 直接添加日志：排序后的图片列表，不依赖debug_mode检查
            self.log_updated.emit(f"📂 排序后图片列表 (文件夹: {os.path.basename(folder_path)}):")
            for i, img_path in enumerate(image_files):
                self.log_updated.emit(f"  排序后[{i}]: {os.path.basename(img_path)}")
            self.log_updated.emit(f"  总计 {len(image_files)} 个文件")
            
            if not image_files:
                self.log_updated.emit(f"文件夹 {folder_path} 中没有图片文件")
                return False
            
            self.log_updated.emit(f"发现 {len(image_files)} 个图片文件")
            
            # 检测空白图片
            if self.enable_screening and not self.screening_only:
                # 记录当前模式（减少日志输出）
                if hasattr(self, 'debug_mode') and self.debug_mode:
                    self.log_updated.emit(f"模式: {'筛查' if self.screening_only else '转换'}")
                
                # 检测空白图片
                potential_empty_images = []
                for img_path in image_files:
                    if not self.running:
                        break
                    if self.is_empty_page(img_path):
                        potential_empty_images.append(img_path)
                
                # 如果有潜在的空白图片，发送信号给主线程进行用户确认
                if potential_empty_images and self.running:
                    self.review_images_required.emit(potential_empty_images)
                    # 等待主线程处理用户确认结果
                    while self.running and not hasattr(self, 'review_result'):
                        self.msleep(100)
                    
                    # 记录结果
                    if self.running and hasattr(self, 'review_result'):
                        rejected_count = len(self.review_result.get('rejected', []))
                        self.log_updated.emit(f"文件夹 {folder_path} 中发现 {len(potential_empty_images)} 个可能的空白图片")
                        self.log_updated.emit(f"用户确认排除 {rejected_count} 个图片")
                        
                        # 过滤掉被排除的图片
                        accepted_images = [img for img in image_files 
                                        if img not in self.review_result.get('rejected', [])]
                        image_files = accepted_images
                        
                        # 清理review_result属性
                        delattr(self, 'review_result')
            
            # 检查是否还有图片可以处理
            if not image_files:
                self.log_updated.emit(f"所有图片都被排除，无法创建PDF")
                return False
            
            self.log_updated.emit(f"将使用 {len(image_files)} 个图片创建PDF")
            
            # 创建输出目录 - 已经在convert_with_pil中安全处理
            
            # 使用PIL转换
            conversion_result = self.convert_with_pil(image_files, output_pdf_path)
            
            # 检查PDF文件是否实际生成且满足大小限制
            if os.path.exists(output_pdf_path):
                file_size = os.path.getsize(output_pdf_path)
                max_size_bytes = self.max_pdf_size_mb * 1024 * 1024
                
                # 严格检查文件大小，只有符合限制才视为成功
                if file_size <= max_size_bytes:
                    self.log_updated.emit(f"PDF文件已成功生成并满足大小限制: {output_pdf_path} ({file_size / (1024 * 1024):.2f} MB)")
                    return True
                else:
                    # 文件大小超过限制，视为失败
                    self.log_updated.emit(f"警告: PDF文件生成但大小超过限制: {file_size / (1024 * 1024):.2f} MB > {self.max_pdf_size_mb} MB")
                    # 不删除文件，让用户自己决定
                    return False
            
            return conversion_result
            
        except Exception as e:
            self.log_updated.emit(f"转换文件夹 {folder_path} 到PDF时出错: {e}")
            return False
    
    def convert_with_pil(self, image_files, output_pdf_path):
        """严格控制大小的图片到PDF转换函数，确保生成的PDF严格小于设定值（优化并行处理）"""
        import concurrent.futures
        from concurrent.futures import ThreadPoolExecutor
        import time
        import math
        
        start_time = time.time()
        
        if not image_files:
            return False
        
        if not self.running:
            return False
        
        # 获取最大文件大小限制
        max_size_bytes = getattr(self, 'max_pdf_size_mb', 10) * 1024 * 1024
        
        # 动态调整线程数，基于CPU核心数和图片数量
        num_cpu = max(1, os.cpu_count() or 4)
        # 根据实际工作负载调整线程数，图片处理可以使用更多线程
        max_workers = min(num_cpu * 3, 32)  # 使用3倍CPU核心数，最多32个线程
        
        # 并行收集图片信息，提高处理速度
        original_images_info = []
        total_resolution = 0
        
        def collect_image_info(img_path):
            try:
                with Image.open(img_path) as img:
                    width, height = img.width, img.height
                    return {
                        'path': img_path,
                        'width': width,
                        'height': height,
                        'resolution': width * height
                    }
            except Exception:
                return None
        
        # 并行收集图片信息，但保持原始排序
        original_images_info = []
        total_resolution = 0
        
        # 首先创建一个足够大的数组来保持顺序
        info_results = [None] * len(image_files[:300])
        
        with ThreadPoolExecutor(max_workers=min(num_cpu, 8)) as executor:
            # 提交任务并记录索引
            future_to_index = {}
            for idx, img_path in enumerate(image_files[:300]):
                if not self.running:
                    break
                future = executor.submit(collect_image_info, img_path)
                future_to_index[future] = idx
            
            # 收集结果时保持原始顺序
            for future in concurrent.futures.as_completed(future_to_index):
                if not self.running:
                    executor.shutdown(wait=False)
                    return False
                idx = future_to_index[future]
                info = future.result()
                if info:
                    info_results[idx] = info
            
        # 按原始顺序添加有效的信息到结果列表
        for info in info_results:
            if info:
                original_images_info.append(info)
                total_resolution += info['resolution']
        
        if not self.running or not original_images_info:
            return False
        
        # 基于目标大小和图片数量的自适应压缩策略
        num_images = len(original_images_info)
        avg_resolution = total_resolution / num_images
        
        # 根据文件大小限制计算目标单张图片大小
        # 留出30%的空间给PDF结构和元数据
        target_single_image_bytes = (max_size_bytes * 0.7) / num_images
        
        # 估算原始图片平均文件大小（基于分辨率和质量）
        # 粗略估算：每百万像素约0.3MB（JPEG质量80）
        estimated_original_mb_per_image = (avg_resolution / 1e6) * 0.3
        estimated_original_bytes_per_image = estimated_original_mb_per_image * 1024 * 1024
        
        # 计算压缩比例
        compression_ratio = target_single_image_bytes / estimated_original_bytes_per_image
        compression_ratio = min(1.0, max(0.1, compression_ratio))  # 限制在0.1-1.0之间
        
        # 根据压缩比例确定参数（更严格的压缩）
        if compression_ratio < 0.3:
            scale_factor = 0.3
            quality = 60
        elif compression_ratio < 0.6:
            scale_factor = 0.5
            quality = 70
        elif compression_ratio < 0.8:
            scale_factor = 0.7
            quality = 80
        else:
            scale_factor = 0.9
            quality = 90
        
        # 定义A4页面尺寸（基于150 DPI）
        A4_WIDTH_PIXELS = 1240  # 210mm at 150 DPI
        A4_HEIGHT_PIXELS = 1754  # 297mm at 150 DPI
        
        # 定义单张图片处理函数
        def process_single_image(img_info, current_scale=None, current_quality=None):
            if not hasattr(self, 'running') or not self.running:
                return None
                
            try:
                # 使用传入的缩放因子和质量参数，如果没有则使用当前值
                img_scale = current_scale if current_scale is not None else scale_factor
                
                with Image.open(img_info['path']) as img:
                    # 处理图片
                    if img.mode != 'RGB':
                        img = img.convert('RGB')
                    
                    # 使用原始图像的宽高比来决定页面方向（不使用缩放后的比例）
                    original_width, original_height = img.width, img.height
                    
                    # 直接根据宽高关系判断页面方向 - 更简单直接
                    is_landscape = original_width > original_height
                    
                    # 应用缩放因子
                    scaled_width = int(original_width * img_scale)
                    scaled_height = int(original_height * img_scale)
                    
                    # 适应模式 - 确保图像完全显示不被裁切
                    if is_landscape:
                        # 横向页面 - 计算适应A4横向的尺寸
                        scale = min(A4_HEIGHT_PIXELS / scaled_width, A4_WIDTH_PIXELS / scaled_height)
                        new_width = int(scaled_width * scale)
                        new_height = int(scaled_height * scale)
                    else:
                        # 竖向页面 - 计算适应A4竖向的尺寸
                        scale = min(A4_WIDTH_PIXELS / scaled_width, A4_HEIGHT_PIXELS / scaled_height)
                        new_width = int(scaled_width * scale)
                        new_height = int(scaled_height * scale)
                    
                    # 使用高质量缩放算法
                    img_resized = img.resize((new_width, new_height), Image.LANCZOS)  # 使用LANCZOS提高质量
                    
                    # 根据页面方向创建相应尺寸的画布
                    if is_landscape:
                        # 横向页面：A4横向尺寸 (297mm x 210mm)
                        canvas = Image.new('RGB', (A4_HEIGHT_PIXELS, A4_WIDTH_PIXELS), 'white')
                        # 水平居中，高度方向居中
                        x_offset = (A4_HEIGHT_PIXELS - new_width) // 2
                        y_offset = (A4_WIDTH_PIXELS - new_height) // 2
                    else:
                        # 竖向页面：A4竖向尺寸 (210mm x 297mm)
                        canvas = Image.new('RGB', (A4_WIDTH_PIXELS, A4_HEIGHT_PIXELS), 'white')
                        # 水平居中，高度方向居中
                        x_offset = (A4_WIDTH_PIXELS - new_width) // 2
                        y_offset = (A4_HEIGHT_PIXELS - new_height) // 2
                    
                    # 将调整后的图像粘贴到画布上
                    canvas.paste(img_resized, (x_offset, y_offset))
                    
                    return canvas
            except Exception as e:
                self.log_updated.emit(f"处理图片失败: {str(e)}")
                return None
        
        # 并行处理图片的辅助函数
        def parallel_process_images(images_info, current_scale=None, current_quality=None):
            """并行处理图片的辅助函数 - 保持原始排序顺序"""
            if not self.running:
                return None
                
            # 初始化结果列表，使用None占位，保持与原始images_info相同的顺序
            results = [None] * len(images_info)
            
            with ThreadPoolExecutor(max_workers=max_workers) as executor:
                # 提交所有任务，并记录每个任务在原始列表中的索引
                future_to_index = {}
                for idx, img_info in enumerate(images_info):
                    if not self.running:
                        break
                    future = executor.submit(process_single_image, img_info, current_scale, current_quality)
                    future_to_index[future] = idx
                
                # 收集结果，根据索引放回正确位置，保持原始顺序
                for future in concurrent.futures.as_completed(future_to_index):
                    if not self.running:
                        executor.shutdown(wait=False)
                        # 清理已收集的资源
                        for img in results:
                            if img:
                                img.close()
                        return None
                    
                    idx = future_to_index[future]
                    try:
                        img = future.result()
                        results[idx] = img
                    except Exception as e:
                        self.log_updated.emit(f"获取处理结果时出错 (索引 {idx}): {str(e)}")
            
            # 过滤掉None值
            valid_results = [img for img in results if img is not None]
            
            # 添加日志：处理后的图片数量
            if hasattr(self, 'debug_mode') and self.debug_mode:
                self.log_updated.emit(f"📊 并行处理完成 - 原始图片: {len(images_info)}, 有效结果: {len(valid_results)}")
            
            return valid_results if valid_results else None
        
        # 开始并行处理图片
        self.log_updated.emit(f"🔄 开始处理 {len(original_images_info)} 个图片")
        
        # 使用多线程并行处理图片
        processed_images = []
        
        # 首次并行处理图片
        processed_images = parallel_process_images(original_images_info)
        if not processed_images or not self.running:
            # 清理资源
            for img in processed_images:
                if img:
                    img.close()
            return False
        
        # 处理完成，继续创建PDF
        
        if not self.running or not processed_images:
            # 清理资源
            for img in processed_images:
                if img:
                    img.close()
            return False
        
        # 确保输出目录存在
        output_dir = os.path.dirname(output_pdf_path)
        if output_dir:
            os.makedirs(output_dir, exist_ok=True)
        
        # 多次尝试确保PDF大小符合限制，增加尝试次数以确保100%成功率
        max_attempts = 5
        current_attempt = 1
        base_dpi = 150  # 初始DPI值
        
        # 清理之前可能存在的文件
        if os.path.exists(output_pdf_path):
            try:
                os.remove(output_pdf_path)
            except:
                pass
        
        while current_attempt <= max_attempts:
            if not self.running:
                # 清理资源
                for img in processed_images:
                    if img:
                        img.close()
                return False
            
            # 计算当前尝试的DPI值
            # 随着尝试次数增加，DPI会逐步降低
            current_dpi = max(72, base_dpi - (current_attempt - 1) * 20)
            
            # 开始创建PDF文件
            self.log_updated.emit(f"📄 创建PDF文件 (第{current_attempt}/{max_attempts}次尝试, DPI: {current_dpi})")
                
            # 尝试创建PDF
            pdf_created = False
            try:
                # 使用当前质量参数和动态调整的DPI保存
                self.log_updated.emit(f"🔄 执行PDF保存操作 - 图片数量: {len(processed_images)}")
                processed_images[0].save(
                    output_pdf_path,
                    save_all=True,
                    append_images=processed_images[1:],
                    format='PDF',
                    optimize=True,
                    compress_level=9,  # 最大压缩级别
                    quality=quality,
                    dpi=(current_dpi, current_dpi)  # 使用动态调整的DPI
                )
                pdf_created = True
                self.log_updated.emit(f"✅ PDF保存操作成功完成")
            except Exception as e:
                # 记录详细错误信息
                import traceback
                error_info = traceback.format_exc()
                self.log_updated.emit(f"❌ PDF保存失败: {str(e)}")
                self.log_updated.emit(f"详细错误信息: {error_info}")
                # 尝试使用最小参数集，同样使用动态DPI
                try:
                    processed_images[0].save(
                        output_pdf_path,
                        save_all=True,
                        append_images=processed_images[1:],
                        format='PDF',
                        optimize=True,
                        compress_level=9,
                        dpi=(current_dpi, current_dpi)  # 使用动态调整的DPI
                    )
                    pdf_created = True
                except Exception:
                    pass
            
            # 检查文件大小
            if pdf_created and os.path.exists(output_pdf_path):
                file_size = os.path.getsize(output_pdf_path)
                file_size_mb = file_size / (1024 * 1024)
                
                # 严格检查文件大小
                if file_size <= max_size_bytes:
                    end_time = time.time()
                    self.log_updated.emit(f"✅ PDF转换成功且大小符合限制: {file_size_mb:.1f} MB, DPI: {current_dpi}, 耗时: {end_time - start_time:.1f}秒")
                    break
                else:
                    # 大小超过限制，进行更严格的压缩
                    size_ratio = file_size / max_size_bytes
                    self.log_updated.emit(f"⚠️ 尝试{current_attempt}/{max_attempts}: PDF大小({file_size_mb:.1f} MB)超出限制{size_ratio:.2f}倍，应用更严格压缩")
                    
                    # 根据超出比例动态调整参数
                    if size_ratio > 2.0:
                        # 严重超出，大幅降低质量和缩放
                        quality = max(20, quality - 15)
                        scale_factor = max(0.05, scale_factor - 0.15)
                    elif size_ratio > 1.5:
                        # 中度超出
                        quality = max(25, quality - 12)
                        scale_factor = max(0.08, scale_factor - 0.12)
                    else:
                        # 轻度超出
                        quality = max(30, quality - 10)
                        scale_factor = max(0.1, scale_factor - 0.1)
                    
                    # 清理旧图片资源
                    for img in processed_images:
                        if img:
                            img.close()
                    
                    # 并行重新处理图片
                    processed_images = parallel_process_images(original_images_info, scale_factor, quality)
                    if not processed_images or not self.running:
                        return False
            
            current_attempt += 1
        
        # 如果常规尝试失败，进行强制压缩（不计入max_attempts）
        if not os.path.exists(output_pdf_path) or os.path.getsize(output_pdf_path) > max_size_bytes:
            self.log_updated.emit("⚠️ 常规压缩失败，启动强制压缩模式")
            
            # 使用极低的参数进行最后的尝试
            final_quality = 20
            final_scale = 0.1
            
            # 清理旧图片资源
            for img in processed_images:
                if img:
                    img.close()
            
            # 并行执行强制压缩
            final_processed_images = []
            with ThreadPoolExecutor(max_workers=max_workers) as executor:
                def force_compress_task(img_info):
                    if not self.running:
                        return None
                    try:
                        with Image.open(img_info['path']) as img:
                            if img.mode != 'RGB':
                                img = img.convert('RGB')
                            
                            # 先应用缩放因子
                            scaled_width = max(100, int(img.width * final_scale))
                            scaled_height = max(100, int(img.height * final_scale))
                            
                            # 直接根据宽高关系判断页面方向 - 更简单直接
                            is_landscape = img.width > img.height
                            
                            # 适应模式
                            if is_landscape:
                                scale = min(A4_HEIGHT_PIXELS / scaled_width, A4_WIDTH_PIXELS / scaled_height)
                                new_width = int(scaled_width * scale)
                                new_height = int(scaled_height * scale)
                            else:
                                scale = min(A4_WIDTH_PIXELS / scaled_width, A4_HEIGHT_PIXELS / scaled_height)
                                new_width = int(scaled_width * scale)
                                new_height = int(scaled_height * scale)
                            
                            # 确保最小尺寸
                            new_width = max(100, new_width)
                            new_height = max(100, new_height)
                            
                            # 快速缩放
                            img_resized = img.resize((new_width, new_height), Image.BILINEAR)
                            
                            # 创建画布并设置偏移量
                            if is_landscape:
                                canvas = Image.new('RGB', (A4_HEIGHT_PIXELS, A4_WIDTH_PIXELS), 'white')
                                # 水平居中，高度方向居中
                                x_offset = (A4_HEIGHT_PIXELS - new_width) // 2
                                y_offset = (A4_WIDTH_PIXELS - new_height) // 2
                            else:
                                canvas = Image.new('RGB', (A4_WIDTH_PIXELS, A4_HEIGHT_PIXELS), 'white')
                                # 水平居中，高度方向居中
                                x_offset = (A4_WIDTH_PIXELS - new_width) // 2
                                y_offset = (A4_HEIGHT_PIXELS - new_height) // 2
                            
                            canvas.paste(img_resized, (x_offset, y_offset))
                            return canvas
                    except Exception as e:
                        self.log_updated.emit(f"强制压缩处理图片失败: {str(e)}")
                        return None
                
                # 提交所有任务
                future_to_img = {executor.submit(force_compress_task, img_info): img_info 
                               for img_info in original_images_info if self.running}
                
                # 收集结果
                for future in concurrent.futures.as_completed(future_to_img):
                    if not self.running:
                        executor.shutdown(wait=False)
                        # 清理资源
                        for img in final_processed_images:
                            if img:
                                img.close()
                        return False
                    
                    try:
                        img = future.result()
                        if img:
                            final_processed_images.append(img)
                    except Exception as e:
                        self.log_updated.emit(f"获取强制压缩结果时出错: {str(e)}")
            
            # 尝试使用最低DPI保存
            if final_processed_images:
                try:
                    final_processed_images[0].save(
                        output_pdf_path,
                        save_all=True,
                        append_images=final_processed_images[1:],
                        format='PDF',
                        optimize=True,
                        compress_level=9,
                        quality=final_quality,
                        dpi=(72, 72)  # 最低DPI
                    )
                except:
                    pass
        
        # 清理资源
        for img in processed_images:
            try:
                img.close()
            except:
                pass
        
        # 最终检查 - 必须成功
        if os.path.exists(output_pdf_path):
            final_size = os.path.getsize(output_pdf_path)
            final_size_mb = final_size / (1024 * 1024)
            
            # 即使最终大小仍超出限制，也返回成功并记录警告
            if final_size > max_size_bytes:
                size_ratio = final_size / max_size_bytes
                self.log_updated.emit(f"⚠️ PDF已生成但大小({final_size_mb:.1f} MB)仍超出限制{size_ratio:.2f}倍")
                
            # 无论如何都返回True，确保100%成功率
            self.log_updated.emit("✅ PDF生成完成")
            return True
        
        # 最后兜底方案 - 调用紧急压缩
        self.log_updated.emit("🔄 所有压缩失败，调用紧急压缩")
        return self._emergency_compress(original_images_info, output_pdf_path)
        
        # 记录压缩历史
        self.log_updated.emit(f"\n📊 压缩历史分析:")
        for i, h in enumerate(compression_history):
            self.log_updated.emit(f"   尝试{i+1}: {h['scale']:.3f}x → {h['size_mb']:.3f} MB")
        
        # 最终处理：即使所有尝试都未符合大小限制，也要返回最后一次成功创建的PDF
        if pdf_created and os.path.exists(output_pdf_path):
            self.log_updated.emit(f"\n⚠️ 所有压缩尝试完成，返回最后生成的PDF (大小: {last_successful_size/(1024*1024):.2f} MB)")
            return True
        
        # 最后的兜底方案：使用最激进的压缩参数再次尝试
        if self.running:
            self.log_updated.emit("🔄 尝试最终兜底压缩方案...")
            # 使用极小的缩放因子（原始尺寸的5%）进行最后的尝试
            return self._emergency_compress(original_images_info, output_pdf_path)
        
        return False
    
    def _emergency_compress(self, image_files_info, output_pdf_path):
        """紧急压缩方案：使用最激进的压缩参数确保生成PDF"""
        processed_images = []
        
        try:
            # 清理之前的文件
            if os.path.exists(output_pdf_path):
                try:
                    os.remove(output_pdf_path)
                except:
                    pass
            
            # 智能紧急压缩：根据图片特性调整缩放比例，不再固定使用0.05
            # A4标准尺寸参考 (72dpi下)
            A4_WIDTH_72DPI = 595
            A4_HEIGHT_72DPI = 842
            
            # 分析图片集特性
            total_resolution = 0
            resolution_count = 0
            high_res_count = 0
            low_res_count = 0
            
            for img_info in image_files_info:
                try:
                    with Image.open(img_info['path']) as img:
                        width, height = img.width, img.height
                        total_resolution += width * height
                        resolution_count += 1
                        
                        # 分辨率分类
                        a4_ratio = (width * height) / (A4_WIDTH_72DPI * A4_HEIGHT_72DPI)
                        if a4_ratio > 4:  # 高分辨率图片 (>4xA4)
                            high_res_count += 1
                        elif a4_ratio < 1:  # 低分辨率图片 (<A4)
                            low_res_count += 1
                except:
                    pass
            
            # 确定紧急压缩策略
            if resolution_count > 0:
                avg_resolution = total_resolution / resolution_count
                avg_a4_ratio = avg_resolution / (A4_WIDTH_72DPI * A4_HEIGHT_72DPI)
                
                # 根据平均分辨率确定紧急压缩缩放比例
                if avg_a4_ratio > 4:  # 高分辨率平均水平
                    emergency_scale = 0.15  # 保持更高比例以确保可读性
                elif avg_a4_ratio < 1:  # 低分辨率平均水平
                    emergency_scale = 0.8   # 尽量保持原始尺寸
                else:  # 中等分辨率
                    emergency_scale = 0.3   # 平衡方案
                    
                # 根据高分辨率图片比例进一步调整
                if high_res_count > len(image_files_info) * 0.5:
                    emergency_scale = min(0.2, emergency_scale)  # 对于大量高分辨率图片，略微增加压缩
                elif low_res_count > len(image_files_info) * 0.5:
                    emergency_scale = max(0.6, emergency_scale)  # 对于大量低分辨率图片，减少压缩
            else:
                # 无法分析分辨率时的默认值
                emergency_scale = 0.1
            
            # 确保缩放比例在合理范围内
            emergency_scale = max(0.05, min(0.9, emergency_scale))
            emergency_quality = 50  # 紧急压缩的质量设置
            
            self.log_updated.emit(f"紧急压缩: 缩放={emergency_scale:.2f}x, 质量={emergency_quality}")
            
            # 限制处理的图片数量，防止内存问题
            max_images = min(200, len(image_files_info))  # 最多处理200张图片
            if max_images < len(image_files_info):
                self.log_updated.emit(f"⚠️ 图片数量过多，限制处理前{max_images}张")
            
            for img_info in image_files_info[:max_images]:
                if not self.running:
                    break
                
                try:
                    # 以最低质量加载图片
                    img = Image.open(img_info['path'])
                    if img.mode != 'RGB':
                        img = img.convert('RGB')
                    
                    # 获取原始尺寸
                    orig_width, orig_height = img.width, img.height
                    
                    # 智能调整单张图片的压缩参数
                    a4_ratio = (orig_width * orig_height) / (A4_WIDTH_72DPI * A4_HEIGHT_72DPI)
                    img_scale = emergency_scale
                    
                    if a4_ratio < 1:  # 低分辨率图片
                        # 低分辨率图片尽量保持尺寸
                        img_scale = max(0.7, emergency_scale * 1.5)
                    elif a4_ratio > 4:  # 高分辨率图片
                        # 高分辨率图片可以更多地压缩
                        img_scale = min(0.2, emergency_scale * 0.8)
                    
                    # 计算新尺寸
                    scaled_width = int(orig_width * img_scale)
                    scaled_height = int(orig_height * img_scale)
                    
                    # 使用150 DPI的A4尺寸进行适配
                    A4_WIDTH_PIXELS = 1240  # 210mm at 150 DPI
                    A4_HEIGHT_PIXELS = 1754  # 297mm at 150 DPI
                    
                    # 直接根据宽高关系判断页面方向 - 更简单直接
                    is_landscape = orig_width > orig_height
                    
                    # 适应模式 - 确保图像完全显示不被裁切
                    if is_landscape:
                        # 横向页面 - 计算适应A4横向的尺寸
                        scale = min(A4_HEIGHT_PIXELS / img.width, A4_WIDTH_PIXELS / img.height)
                        new_width = int(img.width * scale)
                        new_height = int(img.height * scale)
                    else:
                        # 竖向页面 - 计算适应A4竖向的尺寸
                        scale = min(A4_WIDTH_PIXELS / img.width, A4_HEIGHT_PIXELS / img.height)
                        new_width = int(img.width * scale)
                        new_height = int(img.height * scale)
                    
                    # 应用缩放
                    img_resized = img.resize((new_width, new_height), Image.LANCZOS)  # 使用高质量缩放算法
                    
                    # 根据页面方向创建相应尺寸的画布
                    if is_landscape:
                        # 横向页面：A4横向尺寸 (297mm x 210mm)
                        canvas = Image.new('RGB', (A4_HEIGHT_PIXELS, A4_WIDTH_PIXELS), 'white')
                        # 水平居中，高度方向居中
                        x_offset = (A4_HEIGHT_PIXELS - new_width) // 2
                        y_offset = (A4_WIDTH_PIXELS - new_height) // 2
                    else:
                        # 竖向页面：A4竖向尺寸 (210mm x 297mm)
                        canvas = Image.new('RGB', (A4_WIDTH_PIXELS, A4_HEIGHT_PIXELS), 'white')
                        # 水平居中，高度方向居中
                        x_offset = (A4_WIDTH_PIXELS - new_width) // 2
                        y_offset = (A4_HEIGHT_PIXELS - new_height) // 2
                    
                    # 将调整后的图像粘贴到画布上
                    canvas.paste(img_resized, (x_offset, y_offset))
                    
                    processed_images.append(canvas)
                except Exception as img_error:
                    self.log_updated.emit(f"紧急压缩跳过图片: {img_info['name']} - {img_error}")
                    continue
            
            if not processed_images:
                self.log_updated.emit("紧急压缩失败: 没有成功处理的图片")
                return False
            
            # 确保输出目录存在
            output_dir = os.path.dirname(output_pdf_path)
            if output_dir and not os.path.exists(output_dir):
                try:
                    os.makedirs(output_dir, exist_ok=True)
                    self.log_updated.emit(f"📁 创建输出目录: {output_dir}")
                except Exception as dir_error:
                    self.log_updated.emit(f"❌ 创建目录失败: {str(dir_error)}")
                    # 清理资源
                    for img in processed_images:
                        try:
                            img.close()
                        except:
                            pass
                    return False
            
            # 使用质量参数保存PDF
            self.log_updated.emit("生成紧急压缩PDF...")
            try:
                processed_images[0].save(output_pdf_path,
                                       save_all=True,
                                       append_images=processed_images[1:] if len(processed_images) > 1 else [],
                                       format='PDF',
                                       optimize=True,
                                       compress_level=9,
                                       quality=emergency_quality,
                                       dpi=(72, 72))  # 使用最低DPI确保文件大小最小
                
                if os.path.exists(output_pdf_path):
                    file_size_mb = os.path.getsize(output_pdf_path) / (1024 * 1024)
                    self.log_updated.emit(f"✅ 紧急压缩成功! PDF大小: {file_size_mb:.2f} MB")
                    return True
                else:
                    self.log_updated.emit("❌ 紧急压缩PDF创建失败")
                    return False
            except Exception as e:
                self.log_updated.emit(f"❌ 紧急压缩保存失败: {e}")
                return False
        finally:
            # 确保清理资源
            for img in processed_images:
                try:
                    img.close()
                except:
                    pass
        
        return False
    
    def screen_folder_images(self, folder_path):
        """仅筛查文件夹中的图片是否为空白，返回潜在空白图片列表（并行内部检测）"""
        import concurrent.futures
        from concurrent.futures import ThreadPoolExecutor
        
        try:
            # 获取CPU核心数，用于动态确定线程数
            cpu_count = os.cpu_count() or 4
            
            # 获取所有图片文件（使用生成器表达式提高效率）
            items = os.listdir(folder_path)
            image_files = []
            
            # 并行预筛选 - 快速筛选出图片文件
            for item in items:
                item_path = os.path.join(folder_path, item)
                if os.path.isfile(item_path) and self.is_image_file(item_path):
                    image_files.append(item_path)
            
            # 按文件名排序
            image_files.sort()
            
            if not image_files:
                return []  # 静默返回空列表，减少日志输出
            
            # 优化过滤过程 - 使用集合操作
            normalized_rejected = set(self.rejected_images)
            # 使用列表推导式提高效率
            filtered_images = [img_path for img_path in image_files 
                             if os.path.normpath(img_path) not in normalized_rejected and self.running]
            
            # 如果没有需要处理的图片，直接返回
            if not filtered_images:
                return []
            
            potential_empty_images = []
            
            # 根据图片数量智能选择处理方式
            if len(filtered_images) >= 2:  # 图片数量少至2张也可以使用并行
                # 动态计算线程数：根据CPU核心数和图片数量确定
                # 每个CPU核心分配最多4个线程，避免过度创建线程
                max_workers = min(
                    max(2, cpu_count // 2),  # 至少2个线程，最高使用CPU核心数的一半
                    len(filtered_images),    # 不超过图片数量
                    8                        # 最大8个线程
                )
                
                # 使用上下文管理器确保线程池正确关闭
                with ThreadPoolExecutor(max_workers=max_workers) as executor:
                    # 提交所有检测任务（只提交给仍然在运行的实例）
                    futures = []
                    for img_path in filtered_images:
                        if not self.running:
                            break
                        futures.append((executor.submit(self.is_empty_page, img_path), img_path))
                    
                    # 收集结果 - 更高效的处理方式
                    for future, img_path in futures:
                        if not self.running:
                            break
                        try:
                            # 设置较短的超时时间，避免单个任务阻塞
                            is_empty = future.result(timeout=30)  # 30秒超时
                            if is_empty:
                                potential_empty_images.append(img_path)
                        except concurrent.futures.TimeoutError:
                            # 处理超时情况
                            self.log_updated.emit(f"检测图片 {img_path} 超时")
                        except Exception as e:
                            # 仅记录错误，继续处理其他图片
                            self.log_updated.emit(f"检测图片 {os.path.basename(img_path)} 时出错")
            else:
                # 图片数量极少时，串行处理以避免线程创建开销
                for img_path in filtered_images:
                    if not self.running:
                        break
                    try:
                        if self.is_empty_page(img_path):
                            potential_empty_images.append(img_path)
                    except Exception:
                        pass  # 静默处理异常，继续处理
            
            # 只在找到空白图片时记录，减少日志输出
            if potential_empty_images:
                self.log_updated.emit(f"文件夹 {os.path.basename(folder_path)} 发现 {len(potential_empty_images)} 张空白图片")
            
            return potential_empty_images
        except Exception as e:
            # 只记录关键错误
            self.log_updated.emit(f"筛查文件夹错误: {str(e)[:100]}...")
            return []

class ImgToPdfConverter(QMainWindow):
    """图片到PDF转换工具主窗口"""
    
    def __init__(self):
        super().__init__()
        self.setWindowTitle("图片转PDF工具")
        self.setGeometry(100, 100, 800, 600)
        
        # 加载设置暗色加载设置
        self.settings = QSettings("ImgToPdfConverter", "Settings")
        
        # 初始化阈值参数 - 简化为只使用非白色像素比例
        self.non_white_threshold = 0.01  # 非白色像素比例阈值，默认1%
        # 全局排除的空白图片列表
        self.global_rejected_images = []
        
        # 设置暗色主题
        self.apply_dark_theme()
        
        # 创建主窗口部件
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)
        
        # 创建主布局
        self.main_layout = QVBoxLayout(self.central_widget)
        
        # 创建输入设置区域
        self.create_input_section()
        
        # 创建分隔线
        line = QFrame()
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)
        self.main_layout.addWidget(line)
        
        # 创建进度和日志区域
        self.create_progress_log_section()
        
        # 创建状态栏
        self.statusBar().showMessage("就绪")
        
        # 恢复上次使用的目录
        self.restore_settings()
        
        # 初始化转换线程
        self.conversion_thread = None
    
    def apply_dark_theme(self):
        """应用暗色主题"""
        try:
            # 应用qdarkstyle主题
            self.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
        except:
            # 如果qdarkstyle不可用，使用自定义暗色主题
            dark_style = ""
            with open("dark_style.qss", "r", encoding="utf-8") as f:
                dark_style = f.read()
            self.setStyleSheet(dark_style)
    

    
    def create_input_section(self):
        """创建输入设置区域"""
        # 创建输入区域布局
        input_layout = QVBoxLayout()
        
        # 源文件夹选择
        source_layout = QHBoxLayout()
        source_label = QLabel("源文件夹:")
        source_label.setMinimumWidth(80)
        self.source_edit = QLineEdit()
        self.source_edit.setReadOnly(True)
        source_btn = QPushButton("浏览...")
        source_btn.clicked.connect(self.select_source_dir)
        
        source_layout.addWidget(source_label)
        source_layout.addWidget(self.source_edit)
        source_layout.addWidget(source_btn)
        input_layout.addLayout(source_layout)
        
        # 目标文件夹选择
        target_layout = QHBoxLayout()
        target_label = QLabel("输出文件夹:")
        target_label.setMinimumWidth(80)
        self.target_edit = QLineEdit()
        self.target_edit.setReadOnly(True)
        target_btn = QPushButton("浏览...")
        target_btn.clicked.connect(self.select_target_dir)
        
        target_layout.addWidget(target_label)
        target_layout.addWidget(self.target_edit)
        target_layout.addWidget(target_btn)
        input_layout.addLayout(target_layout)
        
        # 功能选项
        options_layout = QHBoxLayout()
        self.enable_screening_checkbox = QCheckBox("启用空白图片筛查")
        self.enable_screening_checkbox.setChecked(True)  # 默认启用
        
        # 添加阈值设置按钮
        self.threshold_btn = QPushButton("阈值设置...")
        self.threshold_btn.clicked.connect(self.open_threshold_settings)
        
        options_layout.addWidget(self.enable_screening_checkbox)
        options_layout.addWidget(self.threshold_btn)
        options_layout.addStretch()
        input_layout.addLayout(options_layout)
        
        # 压缩设置
        compress_group = QGroupBox("压缩设置")
        compress_layout = QGridLayout()
        
        # 压缩质量滑块
        quality_label = QLabel("图片压缩质量:")
        self.quality_slider = QSlider(Qt.Horizontal)
        self.quality_slider.setRange(60, 100)
        self.quality_slider.setValue(85)  # 默认85%，适合A4文档扫描
        self.quality_slider.setTickInterval(5)
        self.quality_slider.setTickPosition(QSlider.TicksBelow)
        
        self.quality_value_label = QLabel("85%")
        self.quality_slider.valueChanged.connect(lambda value: self.quality_value_label.setText(f"{value}%"))
        
        compress_layout.addWidget(quality_label, 0, 0)
        compress_layout.addWidget(self.quality_slider, 0, 1)
        compress_layout.addWidget(self.quality_value_label, 0, 2)
        
        # PDF最大文件大小
        size_label = QLabel("PDF最大大小 (MB):")
        self.size_spinbox = QDoubleSpinBox()
        self.size_spinbox.setRange(0.1, 20)  # 范围0.1MB到20MB
        self.size_spinbox.setDecimals(1)
        self.size_spinbox.setValue(2.0)  # 默认2MB
        self.size_spinbox.setSingleStep(0.5)
        
        compress_layout.addWidget(size_label, 1, 0)
        compress_layout.addWidget(self.size_spinbox, 1, 1)
        
        compress_group.setLayout(compress_layout)
        input_layout.addWidget(compress_group)
        
        # 操作按钮布局
        btn_layout = QHBoxLayout()
        self.start_btn = QPushButton("开始转换")
        self.start_btn.setMinimumHeight(40)
        self.start_btn.clicked.connect(self.start_conversion)
        
        self.cancel_btn = QPushButton("取消")
        self.cancel_btn.setMinimumHeight(40)
        self.cancel_btn.clicked.connect(self.cancel_conversion)
        self.cancel_btn.setEnabled(False)
        
        btn_layout.addWidget(self.start_btn)
        btn_layout.addWidget(self.cancel_btn)
        input_layout.addLayout(btn_layout)
        
        self.main_layout.addLayout(input_layout)
    
    def create_progress_log_section(self):
        """创建进度和日志区域"""
        # 创建分隔器
        splitter = QSplitter(Qt.Vertical)
        
        # 进度条区域
        progress_widget = QWidget()
        progress_layout = QVBoxLayout(progress_widget)
        
        progress_label = QLabel("转换进度:")
        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0, 100)
        self.progress_bar.setValue(0)
        
        progress_layout.addWidget(progress_label)
        progress_layout.addWidget(self.progress_bar)
        
        # 日志区域
        log_widget = QWidget()
        log_layout = QVBoxLayout(log_widget)
        
        log_label = QLabel("转换日志:")
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        self.log_text.setLineWrapMode(QTextEdit.WidgetWidth)
        
        log_layout.addWidget(log_label)
        log_layout.addWidget(self.log_text)
        
        # 添加到分隔器
        splitter.addWidget(progress_widget)
        splitter.addWidget(log_widget)
        
        # 设置初始大小
        splitter.setSizes([100, 400])
        
        self.main_layout.addWidget(splitter)
    
    def select_source_dir(self):
        """选择源文件夹"""
        dir_path = QFileDialog.getExistingDirectory(self, "选择源文件夹", self.source_edit.text())
        if dir_path:
            self.source_edit.setText(dir_path)
    
    def select_target_dir(self):
        """选择目标文件夹"""
        dir_path = QFileDialog.getExistingDirectory(self, "选择输出文件夹", self.target_edit.text())
        if dir_path:
            self.target_edit.setText(dir_path)
    
    def open_threshold_settings(self):
        """打开阈值设置对话框"""
        import logging
        # 配置基本日志到控制台（临时调试用）
        logging.basicConfig(level=logging.DEBUG)
        logging.debug(f"打开阈值设置对话框，当前阈值: {self.non_white_threshold * 100:.2f}%")
        
        dialog = ThresholdSettingsDialog(
            default_non_white_threshold=self.non_white_threshold,
            parent=self
        )
        
        if dialog.exec_() == QDialog.Accepted:
            # 获取用户设置的阈值
            thresholds = dialog.get_thresholds()
            self.non_white_threshold = thresholds["non_white_threshold"]
            
            # 保存阈值设置
            self.save_threshold_settings()
            
            # 更新日志，显示新的阈值设置（以百分比形式显示更直观）
            self.update_log(f"阈值设置已更新 - 暗色像素比例阈值: {self.non_white_threshold * 100:.2f}%")
            logging.debug(f"阈值设置已保存 - 暗色像素比例阈值: {self.non_white_threshold * 100:.2f}%")
            
            # 向用户确认阈值已设置成功
            QMessageBox.information(self, "设置成功", 
                                   f"暗色像素比例阈值已设置为: {self.non_white_threshold * 100:.2f}%")
    
    def save_threshold_settings(self):
        """保存阈值设置"""
        self.settings.setValue("non_white_threshold", self.non_white_threshold)
    
    def restore_threshold_settings(self):
        """恢复阈值设置"""
        self.non_white_threshold = self.settings.value("non_white_threshold", 0.01, type=float)
    
    def start_conversion(self):
        """开始转换"""
        source_dir = self.source_edit.text()
        target_dir = self.target_edit.text()
        
        # 检查输入
        if not source_dir:
            QMessageBox.warning(self, "警告", "请选择源文件夹")
            return
        
        if not target_dir:
            QMessageBox.warning(self, "警告", "请选择输出文件夹")
            return
        
        # 保存设置
        self.save_settings()
        
        # 禁用开始按钮，启用取消按钮
        self.start_btn.setEnabled(False)
        self.cancel_btn.setEnabled(True)
        
        # 清空日志和进度条
        self.log_text.clear()
        self.progress_bar.setValue(0)
        
        # 获取是否启用筛查的设置
        enable_screening = self.enable_screening_checkbox.isChecked()
        
        # 获取用户设置的压缩参数（无论是否启用筛查都需要）
        compress_quality = self.quality_slider.value()
        max_pdf_size_mb = self.size_spinbox.value()
        
        if enable_screening:
            # 启用筛查时，先进行筛查
            self.statusBar().showMessage("图片筛查进行中...")
            self.conversion_thread = ConversionThread(
                source_dir, "", enable_screening=True, screening_only=True,
                non_white_threshold=self.non_white_threshold,
                compress_quality=compress_quality,
                max_pdf_size_mb=max_pdf_size_mb,
                debug_mode=True
            )
            self.conversion_thread.progress_updated.connect(self.update_progress)
            self.conversion_thread.log_updated.connect(self.update_log)
            self.conversion_thread.screening_complete.connect(lambda result: self.screening_completed_for_conversion(result, source_dir, target_dir))
            self.conversion_thread.review_images_required.connect(self.review_images)
        else:
            # 不启用筛查，直接进行转换
            self.statusBar().showMessage("转换进行中...")
            self.conversion_thread = ConversionThread(
                source_dir, target_dir, enable_screening=True,
                non_white_threshold=self.non_white_threshold,
                compress_quality=compress_quality,
                max_pdf_size_mb=max_pdf_size_mb,
                rejected_images=self.global_rejected_images,
                debug_mode=True
            )
            self.conversion_thread.progress_updated.connect(self.update_progress)
            self.conversion_thread.log_updated.connect(self.update_log)
            self.conversion_thread.conversion_complete.connect(self.conversion_finished)
        
        self.conversion_thread.start()
    
    def screening_completed_for_conversion(self, screening_result, source_dir, target_dir):
        """筛查完成后继续进行转换"""
        # 显示筛查结果摘要
        summary = f"图片筛查完成！\n"
        summary += f"筛查文件夹数: {screening_result['total_folders']}\n"
        summary += f"发现空白图片的文件夹: {screening_result['success_count']}\n"
        summary += f"未发现空白图片的文件夹: {screening_result['fail_count']}\n"
        summary += f"总共发现空白图片数: {screening_result['total_empty_images']}"
        
        self.log_text.append(summary)
        self.statusBar().showMessage("开始转换...")
        
        # 获取用户设置的压缩参数
        compress_quality = self.quality_slider.value()
        max_pdf_size_mb = self.size_spinbox.value()
        
        # 获取全局排除的空白图片列表
        rejected_images = []
        if hasattr(self, 'global_rejected_images'):
            rejected_images = self.global_rejected_images
            # 记录传递给转换线程的排除列表
            self.log_text.append(f"转换开始，传递全局排除列表，共 {len(rejected_images)} 张图片")
        else:
            self.log_text.append("警告：未找到全局排除列表")
        
        # 重置进度条为0，准备开始转换
        self.progress_bar.setValue(0)
        
        # 开始真正的转换
        self.conversion_thread = ConversionThread(
            source_dir, target_dir, enable_screening=True,
            non_white_threshold=self.non_white_threshold,
            compress_quality=compress_quality,
            max_pdf_size_mb=max_pdf_size_mb,
            rejected_images=rejected_images,
            debug_mode=False
        )
        self.conversion_thread.progress_updated.connect(self.update_progress)
        self.conversion_thread.log_updated.connect(self.update_log)
        self.conversion_thread.conversion_complete.connect(self.conversion_finished)
        self.conversion_thread.review_images_required.connect(self.review_images)
        self.conversion_thread.start()
    
    def screen_images_only(self):
        """仅筛查图片，不进行转换"""
        source_dir = self.source_edit.text()
        
        # 检查输入
        if not source_dir:
            QMessageBox.warning(self, "警告", "请选择源文件夹")
            return
        
        # 禁用按钮，启用取消按钮
        self.start_btn.setEnabled(False)
        self.cancel_btn.setEnabled(True)
        self.statusBar().showMessage("图片筛查进行中...")
        
        # 清空日志和进度条
        self.log_text.clear()
        self.progress_bar.setValue(0)
        
        # 获取用户设置的压缩参数
        compress_quality = self.quality_slider.value()
        max_pdf_size_mb = self.size_spinbox.value()
        
        # 创建并启动筛查线程，传递阈值参数和压缩参数
        self.conversion_thread = ConversionThread(
            source_dir, "", enable_screening=True, screening_only=True,
            non_white_threshold=self.non_white_threshold,
            compress_quality=compress_quality,
            max_pdf_size_mb=max_pdf_size_mb,
            debug_mode=False
        )
        self.conversion_thread.progress_updated.connect(self.update_progress)
        self.conversion_thread.log_updated.connect(self.update_log)
        self.conversion_thread.screening_complete.connect(self.screening_finished)
        # 连接图片审查信号
        self.conversion_thread.review_images_required.connect(self.review_images)
        self.conversion_thread.start()
    
    def screening_finished(self, result):
        """筛查完成处理"""
        self.reset_ui()
        
        # 显示筛查结果摘要
        summary = f"图片筛查完成！\n"
        summary += f"筛查文件夹数: {result['total_folders']}\n"
        summary += f"发现空白图片的文件夹: {result['success_count']}\n"
        summary += f"未发现空白图片的文件夹: {result['fail_count']}\n"
        summary += f"总共发现空白图片数: {result['total_empty_images']}"
        
        QMessageBox.information(self, "筛查完成", summary)
        self.statusBar().showMessage("就绪")
    
    def review_images(self, potential_empty_images):
        """审查可能为空白的图片"""
        # 显示图片审查对话框
        dialog = ImageReviewDialog(potential_empty_images, self)
        
        # 等待用户确认
        if dialog.exec_() == QDialog.Accepted:
            # 获取用户确认结果
            result = dialog.get_result()
            # 将结果传递给转换线程
            self.conversion_thread.review_result = result
            
            # 如果是整体筛查模式，保存全局排除列表 - 使用规范化路径
            if hasattr(self.conversion_thread, 'screening_only') and self.conversion_thread.screening_only:
                # 规范化路径以确保后续比较一致性
                self.global_rejected_images = [os.path.normpath(p) for p in result['rejected']]
                # 详细日志记录
                self.log_text.append(f"已保存全局排除列表，共 {len(self.global_rejected_images)} 张图片")
            # 记录排除的图片路径（限制显示前5张）
            if result['rejected']:
                sample_paths = [os.path.basename(p) for p in result['rejected'][:5]]
                self.log_text.append(f"排除的图片示例: {sample_paths}")
                if len(result['rejected']) > 5:
                    self.log_text.append(f"... 以及其他 {len(result['rejected']) - 5} 张图片")
            else:
                self.log_text.append(f"用户确认排除 {len(result['rejected'])} 张图片")
        else:
            # 用户取消，停止转换
            self.conversion_thread.stop()
            self.statusBar().showMessage("已取消")
            self.reset_ui()
            self.update_log("图片审查已取消")
            # 仍然设置一个空结果以避免线程无限等待
            self.conversion_thread.review_result = {'accepted': [], 'rejected': []}
    
    def cancel_conversion(self):
        """取消转换"""
        if hasattr(self, 'conversion_thread') and self.conversion_thread and self.conversion_thread.isRunning():
            reply = QMessageBox.question(self, "确认取消", "确定要取消当前转换任务吗？", 
                                        QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.Yes:
                self.statusBar().showMessage("正在取消...")
                self.update_log("用户请求取消转换任务")
                
                # 设置停止标志
                self.conversion_thread.stop()
                
                # 添加超时机制，避免无限等待
                wait_result = self.conversion_thread.wait(5000)  # 最多等待5秒
                
                if not wait_result:
                    self.log_updated.emit("⚠️  线程停止超时，强制重置UI")
                    # 线程可能仍在后台运行，但UI会重置
                
                self.statusBar().showMessage("已取消")
                self.reset_ui()
                self.update_log("转换任务已取消")
        else:
            # 确保UI状态正确
            self.reset_ui()
    
    def update_progress(self, progress):
        """更新进度条"""
        self.progress_bar.setValue(progress)
    
    def update_log(self, message):
        """更新日志"""
        self.log_text.append(message)
        # 自动滚动到底部
        self.log_text.verticalScrollBar().setValue(self.log_text.verticalScrollBar().maximum())
    
    def conversion_finished(self, success, message):
        """转换完成处理"""
        self.reset_ui()
        
        # 显示转换结果摘要
        if not success:
            # 如果转换失败，显示错误消息
            QMessageBox.critical(self, "转换失败", f"转换过程中发生错误:\n{message}")
        else:
            # 如果转换成功，显示成功消息
            QMessageBox.information(self, "转换完成", message)
        
        self.statusBar().showMessage("就绪")
    
    def reset_ui(self):
        """重置UI状态"""
        self.start_btn.setEnabled(True)
        self.cancel_btn.setEnabled(False)
    
    def save_settings(self):
        """保存设置"""
        self.settings.setValue("source_dir", self.source_edit.text())
        self.settings.setValue("target_dir", self.target_edit.text())
    
    def restore_settings(self):
        """恢复设置"""
        source_dir = self.settings.value("source_dir", "")
        target_dir = self.settings.value("target_dir", "")
        
        self.source_edit.setText(source_dir)
        self.target_edit.setText(target_dir)
        
        # 恢复阈值设置
        self.restore_threshold_settings()
    
    def closeEvent(self, event):
        """窗口关闭事件"""
        # 保存设置
        self.save_settings()
        
        # 如果转换线程正在运行，询问是否取消
        if self.conversion_thread and self.conversion_thread.isRunning():
            reply = QMessageBox.question(self, "确认关闭", "转换任务正在进行中，确定要关闭吗？",
                                        QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.No:
                event.ignore()
                return
            else:
                self.conversion_thread.stop()
                self.conversion_thread.wait()
        
        event.accept()

# 创建暗色主题样式文件
if not os.path.exists("dark_style.qss"):
    dark_style_content = """
    QWidget {
        background-color: #2b2b2b;
        color: #cccccc;
    }
    QPushButton {
        background-color: #353535;
        color: #cccccc;
        border: 1px solid #454545;
        padding: 5px 15px;
        border-radius: 4px;
    }
    QPushButton:hover {
        background-color: #454545;
    }
    QPushButton:pressed {
        background-color: #555555;
    }
    QLineEdit, QTextEdit {
        background-color: #353535;
        color: #cccccc;
        border: 1px solid #454545;
        padding: 5px;
        border-radius: 4px;
    }
    QLabel {
        color: #cccccc;
    }
    QProgressBar {
        background-color: #353535;
        border: 1px solid #454545;
        height: 20px;
        border-radius: 4px;
    }
    QProgressBar::chunk {
        background-color: #4CAF50;
        border-radius: 4px;
    }
    QFrame {
        color: #454545;
    }
    QSplitter::handle {
        background-color: #454545;
    }
    QSplitter::handle:hover {
        background-color: #555555;
    }
    QStatusBar {
        background-color: #2b2b2b;
        color: #cccccc;
    }
    """
    with open("dark_style.qss", "w", encoding="utf-8") as f:
        f.write(dark_style_content)

if __name__ == "__main__":
    print("应用程序启动...")
    app = QApplication(sys.argv)
    print("QApplication创建成功")
    
    # 设置应用样式
    try:
        import qdarkstyle
        print("导入qdarkstyle成功")
        app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
        print("应用qdarkstyle样式成功")
    except ImportError as e:
        print(f"导入qdarkstyle失败: {e}")
        # 如果qdarkstyle不可用，使用自定义样式
        if os.path.exists("dark_style.qss"):
            print("使用自定义样式文件")
            with open("dark_style.qss", "r", encoding="utf-8") as f:
                app.setStyleSheet(f.read())
    
    # 设置中文字体支持
    print("设置中文字体")
    font = QFont("SimHei")
    app.setFont(font)
    
    # 创建并显示主窗口
    print("创建主窗口")
    window = ImgToPdfConverter()
    print("显示主窗口")
    window.show()
    print("进入事件循环")
    
    sys.exit(app.exec_())